
namespace Behemoth.Office.Server {

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Text;

    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;

    using Microsoft.SharePoint;
    using Microsoft.Office.Server;
    using Microsoft.Office.Server.UserProfiles;

    using Behemoth.SharePoint.Logging;

    /// <summary>
    /// A useful developer code pattern that for provisioning user profile properties.
    /// We took the example from http://www.mylifeinaminute.com/2008/12/16/creating-custom-profile-properties-through-code-c/
    /// and made an abstract class so you can just change the important parts.
    /// </summary>
    public abstract class ProfilePropertyCreator : IDisposable {

        protected BehemothLoggingService _log = new BehemothLoggingService();
        protected SPSite _site;
        private bool _shouldDisposeSite = false;
        protected List<string> _propertyNames = null;

        public ProfilePropertyCreator(SPSite site) {
            _site = site;
        }
        public ProfilePropertyCreator() {
            _site = SPContext.GetContext(HttpContext.Current).Site;
            // TODO what to do when we can't get the context?
            _shouldDisposeSite = true;
        }

        ~ProfilePropertyCreator() {
            this.Dispose();
        }
        public void Dispose() {
            if (_shouldDisposeSite && _site != null)
                _site.Dispose();
        }

        private void PopulatePropertyNames(PropertyCollection properties) {
            _propertyNames = new List<string>();
            foreach (Property property in properties) {
                _propertyNames.Add(property.Name);
            }
        }
        protected bool PropertyExists(string propertyName) {
            if (_propertyNames == null)
                throw new Exception("Can not call this method before PopulatePropertyNames(...)");
            return (_propertyNames.Contains(propertyName));
        }

        /// <summary>
        /// Override this method to provision your user profile properties.
        /// </summary>
        /// <param name="propertyCollection"></param>
        /// <example>
        /// Developers should implement this class with one or more properties
        /// like the following:
        /// <code>
        /// public override void CreateProperties(UserProfileConfigManager profileConfigManager) {
        ///   PropertyCollection propertyCollection = profileConfigManager.GetProperties();
        ///   if (!PropertyExists("ExampleProperty")) {
        ///     Property property = propertyCollection.Create(false);
        ///     property.Name = "ExampleProperty";
        ///     property.DisplayName = "Example Property";
        ///     property.Type = "string";
        ///     property.PrivacyPolicy = PrivacyPolicy.OptIn;
        ///     property.DefaultPrivacy = Privacy.Private;
        ///     property.Description = "ExampleProperty";
        ///     property.IsSearchable = false;
        ///     property.IsVisibleOnEditor = false;
        ///     property.IsAlias = false;
        ///     property.Length = 100;
        ///     property.IsUserEditable = true;
        ///     propertyCollection.Add(property);
        ///   }
        /// }
        /// </code>
        /// </example>
        protected abstract void CreateProperties(UserProfileConfigManager profileConfigManager);

        public void EnsureUserProfileProperties() {
            HttpContext savedContext = HttpContext.Current;
            SPSecurity.RunWithElevatedPrivileges(
              delegate() {
                  using (SPSite elevatedSite = new SPSite(_site.ID)) {
                      try {
                          // set up access to the site so we can write to it
                          HttpContext.Current = null;
                          elevatedSite.AllowUnsafeUpdates = true;
                          elevatedSite.RootWeb.AllowUnsafeUpdates = true;

                          // set up the required profile objects
                          ServerContext serverContext = ServerContext.GetContext(elevatedSite);
                          UserProfileManager profileManager = new UserProfileManager(serverContext);
                          UserProfileConfigManager profileConfigManager = new UserProfileConfigManager(serverContext);

                          PopulatePropertyNames(profileManager.Properties);
                          CreateProperties(profileConfigManager);

                      } catch (Exception ex) {
                          _log.Write(ex);
                          throw ex; // does this interfere with Dispose?
                      } finally {
                          elevatedSite.Dispose();
                      }
                  }
              });
            HttpContext.Current = savedContext;
        }

        protected UserProfile GetProfile() {
            return GetProfile(this._site);
        }
        protected UserProfile GetProfile(SPSite site) {
            ServerContext serverContext = ServerContext.GetContext(site);
            UserProfileManager profileManager = new UserProfileManager(serverContext);
            //UserProfileConfigManager profileConfigManager = new UserProfileConfigManager(serverContext);
            //PropertyCollection properties = profileConfigManager.GetProperties();
            UserProfile profile = profileManager.GetUserProfile(true);
            return profile;
        }

        protected abstract string[] AllowedPropertyNames {
            get;
        }

        protected event ElevatedPropertySaveEventHandler SaveProperties;

        private UserProfile _currentProfile;
        protected virtual void OnSaveProperties(SPSite elevatedSite, MyPropertySaveEventArgs args) {
            _currentProfile = GetProfile(elevatedSite);
            if (SaveProperties != null) {
                SaveProperties(this, args);
            }
            _currentProfile.Commit();
            _currentProfile = null;
            SaveProperties = null;
        }

        public delegate void ElevatedPropertySaveEventHandler(object sender, MyPropertySaveEventArgs e);

        // TODO support a name value collection instead
        public class MyPropertySaveEventArgs : EventArgs {

            public MyPropertySaveEventArgs(string propertyName, object value) {
                _propertyName = propertyName;
                _value = value;
            }

            private string _propertyName;
            public string PropertyName {
                get { return _propertyName; }
            }

            private object _value;
            public object Value {
                get { return _value; }
            }
        }

        protected void DefaultSaveSettingsEvent(object sender, MyPropertySaveEventArgs e) {
            if (_currentProfile == null || AllowedPropertyNames == null)
                return;
            foreach (string allowedName in AllowedPropertyNames) {
                if (string.Compare(e.PropertyName, allowedName, true) == 0) {

                    SPSecurity.RunWithElevatedPrivileges(delegate() {
                        using (SPSite elevatedSite = new SPSite(_site.ID)) {
                            try {
                                // NOTE just in case previous object didn't have enough privleges
                                _currentProfile = GetProfile(elevatedSite);

                                _currentProfile[e.PropertyName].Value = null;
                                _currentProfile[e.PropertyName].Clear();
                                // there is a weird thing in Value set where it will loop through a
                                // collection. Unfortunately that's not what we want for binary arrays.
                                if (_currentProfile[e.PropertyName].Property.Type == "binary")
                                    _currentProfile[e.PropertyName].Add((byte[])e.Value);
                                else
                                    _currentProfile[e.PropertyName].Value = e.Value;
                                // TODO implement other types here

                            } catch (Exception ex) {
                                _log.Write(ex);
                                throw ex; // does this interfere with Dispose?
                            } finally {
                                elevatedSite.Dispose();
                            } // try
                        } // using
                    }); // delegate
                }
            }
        }

        protected void SaveProperties_Elevated(MyPropertySaveEventArgs args) {
            SaveProperties = null; // clear all previous events
            SaveProperties += new ElevatedPropertySaveEventHandler(this.DefaultSaveSettingsEvent);
            //HttpContext savedContext = HttpContext.Current;
            SPSecurity.RunWithElevatedPrivileges(
              delegate() {
                  using (SPSite elevatedSite = new SPSite(_site.ID)) {
                      try {
                          bool contextAllowUnsafe = SPContext.Current.Web.AllowUnsafeUpdates;
                          // set up access to the site so we can write to it
                          //HttpContext.Current = null;
                          SPContext.Current.Web.AllowUnsafeUpdates = true;
                          elevatedSite.AllowUnsafeUpdates = true;
                          elevatedSite.RootWeb.AllowUnsafeUpdates = true;

                          OnSaveProperties(elevatedSite, args);

                          SPContext.Current.Web.AllowUnsafeUpdates = contextAllowUnsafe;
                      } catch (Exception ex) {
                          _log.Write(ex);
                          throw ex; // does this interfere with Dispose?
                      } finally {
                          elevatedSite.Dispose();
                      } // try
                  } // using
              }); // delegate
            //HttpContext.Current = savedContext;
        }

        public static PropertyDataType GetPropertyType(UserProfileConfigManager profileConfigManager, string name) {
            //sample to get a property type "URL"
            PropertyDataTypeCollection pdtc = profileConfigManager.GetPropertyDataTypes();
            PropertyDataType ptype = null;
            IEnumerator enumType = pdtc.GetEnumerator();
            while (enumType.MoveNext()) {
                ptype = (PropertyDataType)enumType.Current;
                if (string.Compare(name, ptype.Name, true) == 0) break;
            }
            return ptype;
        }

    } // class

} // namespace
